using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Xml;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.Windows;

using miciv.Rendering;
using miciv.Misc;

namespace miciv
{
    public class Frame : RenderableBase, ISize, IPosition, IParameterizable, ILayout
    {
        public delegate void SizeChangedEventHandler();
        public delegate void WorldMatrixChangedEventHandler();

        public event SizeChangedEventHandler SizeChanged;
        public event WorldMatrixChangedEventHandler WorldMatrixChanged;

        protected Vector2 m_f2Size;
        protected Vector2 m_f2HalfSize;
        protected Vector2 m_f2Position;
        protected Vector2 m_f2Scale;
        protected string m_strID;
        protected Frame m_frmParent;
        protected Matrix m_matLocal;
        protected List<Frame> m_lstChildren;
        protected Hashtable m_htblChildren;
        protected string m_strScriptControlID;
        protected float m_fZOrder;
        protected bool m_bVisible;
        protected bool m_bDirty;
        protected object m_objScriptControl;
        protected string m_strScriptControlPath;
        protected Hashtable m_htblParameters;
        protected int m_sDataSourceIndex;
        protected IDataSource m_dataSource;
        protected string m_dataSourceName;
        protected bool m_bInheritsDataSource;
        protected LayoutProcessor m_layoutProcessor;
        protected IUIManager m_uiManager;
        protected bool m_bAutoResize;

        public Frame(CoreContext _coreContext, string _strID)
            : base(_coreContext)
        {
            InternalInitialize(_coreContext, _strID);
        }

        public Frame(CoreContext _coreContext, string _strID, Frame _frmParent)
            : base(_coreContext)
        {
            InternalInitialize(_coreContext, _strID);
            this.Parent = _frmParent;
            if (null != this.Parent)
            {
                this.Size = this.Parent.Size;
            }
        }

        virtual protected void InternalInitialize(CoreContext _coreContext, string _strID)
        {
            this.Size = new Vector2(0.0f, 0.0f);
            this.Position = new Vector2(0.0f, 0.0f);
            this.Scale = new Vector2(1.0f, 1.0f);
            this.m_strID = _strID;
            this.Parent = null;
            this.m_renderer = this.m_coreContext.GetData<RendererManager>("RendererManager").Get("NullRenderer");
            this.m_matLocal = Matrix.Identity;
            this.m_lstChildren = new List<Frame>();
            this.m_htblChildren = new Hashtable();
            this.m_fZOrder = -0.1f;
            this.m_bVisible = true;
            this.m_bDirty = true;
            this.m_objScriptControl = null;
            this.m_strScriptControlPath = "";
            this.m_htblParameters = null;
            this.m_sDataSourceIndex = -1;
            this.m_dataSource = null;
            this.m_dataSourceName = string.Empty;
            this.m_uiManager = this.m_coreContext.GetData<IUIManager>("UIManager");
            this.AutoResize = false;
        }

        #region Properties

        virtual public Vector2 Size
        {
            get
            {
                return this.m_f2Size;
            }
            set
            {
                this.m_f2Size = value;
                this.m_f2HalfSize = this.m_f2Size / 2.0f;
                this.m_bDirty = true;
                if (null != this.SizeChanged)
                {
                    this.SizeChanged();
                }
            }
        }

        virtual public Vector2 Position
        {
            get
            {
                return this.m_f2Position;
            }
            set
            {
                this.m_f2Position = value;
                this.m_bDirty = true;
                this.UpdateLocalMatrix();
            }
        }

        virtual public Vector2 Scale
        {
            get
            {
                return this.m_f2Scale;
            }
            set
            {
                this.m_f2Scale = value;
                this.m_bDirty = true;
                this.UpdateLocalMatrix();
            }
        }

        virtual public string ID
        {
            get
            {
                return this.m_strID;
            }
        }

        virtual public bool Visible
        {
            set
            {
                this.m_bVisible = value;
            }
            get
            {
                return this.m_bVisible;
            }
        }

        virtual public Frame Parent
        {
            set
            {
                if (null != this.m_frmParent)
                {
                    this.m_frmParent.SizeChanged -= this.OnParentSizeChanged;
                    this.m_frmParent.WorldMatrixChanged -= this.OnParentWorldMatrixChanged;
                    this.m_frmParent.RemoveChild(this);
                }

                this.m_frmParent = value;

                if (null != this.m_frmParent)
                {
                    this.m_frmParent.SizeChanged += this.OnParentSizeChanged;
                    this.m_frmParent.WorldMatrixChanged += this.OnParentWorldMatrixChanged;
                    this.m_frmParent.AddChild(this);
                }

                this.UpdateLocalMatrix();
            }
            get
            {
                return this.m_frmParent;
            }
        }

        virtual public string ScriptControlID
        {
            get
            {
                return this.m_strScriptControlID;
            }
            set
            {
                this.m_strScriptControlID = value;
            }
        }

        virtual public object ScriptControl
        {
            get
            {
                return this.m_objScriptControl;
            }
            set
            {
                this.m_objScriptControl = value;
            }
        }

        virtual public string ScriptControlPath
        {
            get
            {
                return this.m_strScriptControlPath;
            }
            set
            {
                this.m_strScriptControlPath = value;
            }
        }

        virtual public float ZOrder
        {
            get
            {
                return this.m_fZOrder;
            }
        }

        virtual public List<Frame> Children
        {
            get
            {
                return this.m_lstChildren;
            }
        }

        virtual public LayoutProcessor LayoutProcessor
        {
            get
            {
                return this.m_layoutProcessor;
            }
        }

        virtual public bool NeedUpdate
        {
            get
            {
                return ((false != this.m_bVisible) // frame must be visible
                    && ((false != this.m_bDirty) // and tagged as dirty
                    || (false != this.NeedUpdateFromDataSource))); // or is a valid candidate for update drom data source
            }
        }

        virtual public bool NeedUpdateFromDataSource
        {
            get
            {
                return ((null != this.m_dataSource) // must have a non null data source
                    //&& (false != this.m_dataSource.HasChanged)
                    && (-1 < this.m_sDataSourceIndex) // a valid index in the data source access range
                    && (this.m_dataSource.Count > this.m_sDataSourceIndex)
                    && (null != this.m_htblParameters) // and requires parameters from the data source
                    && (0 < this.m_htblParameters.Count));
            }
        }

        virtual public bool AutoResize
        {
            get
            {
                return this.m_bAutoResize;
            }
            set
            {
                this.m_bAutoResize = value;
            }
        }

        #endregion

        virtual public void AddToRenderer()
        {
            if (null != m_renderer)
            {
                this.m_renderer.Add(this);
            }
            foreach (Frame frame in this.m_lstChildren)
            {
                if (false != frame.Visible)
                {
                    frame.AddToRenderer();
                }
            }
        }

        virtual protected void OnParentSizeChanged()
        {

        }

        virtual protected void OnParentWorldMatrixChanged()
        {
            this.m_matWorld = this.m_matLocal;
            if (null != this.m_frmParent)
            {
                this.m_matWorld *= this.m_frmParent.World;
            }
            if (null != WorldMatrixChanged)
            {
                WorldMatrixChanged();
            }
        }

        virtual public void AddChild(Frame _frame)
        {
            if (false == this.m_htblChildren.ContainsKey(_frame.ID))
            {
                this.m_htblChildren.Add(_frame.ID, _frame);
                this.m_lstChildren.Add(_frame);
            }
            else
            {
                throw new Exception("there's already a child named " + _frame.ID + " in frame " + ID);
            }
        }

        virtual public void RemoveChild(Frame _frame)
        {
            if (false != this.m_htblChildren.ContainsKey(_frame.ID))
            {
                this.m_htblChildren.Remove(_frame.ID);
                this.m_lstChildren.Remove(_frame);
                _frame.Parent = null;
            }
        }

        virtual public Frame GetChild(string _strChildID)
        {
            if (false != this.m_htblChildren.ContainsKey(_strChildID))
            {
                return this.m_htblChildren[_strChildID] as Frame;
            }
            return null;
        }

        virtual public void PickChildren(Stack<Frame> _pickedFrames, float _fX, float _fY)
        {
            if (false != IsInBounds(_fX, _fY))
            {
                if (null != this.ScriptControl)
                {
                    _pickedFrames.Push(this);
                }
                _fX -= this.m_f2Position.X;
                _fY -= this.m_f2Position.Y;
                foreach (Frame child in this.m_lstChildren)
                {
                    child.PickChildren(_pickedFrames, _fX, _fY);
                }
            }
        }

        virtual public void ReleaseAllChildren()
        {
            while (0 < this.m_lstChildren.Count)
            {
                Frame child = this.m_lstChildren[0];
                child.Release();
                child.Parent = null;
            }
            this.m_htblChildren.Clear();
            this.m_lstChildren.Clear();
        }

        virtual public Frame Clone(Frame _frmParent, string _strID)
        {
            Frame frame = new Frame(this.m_coreContext, this.m_strID + _strID, _frmParent);
            frame.Copy(this);
            foreach (Frame child in this.m_lstChildren)
            {
                child.Clone(frame);
            }
            return frame;
        }

        virtual public Frame Clone(Frame _frmParent)
        {
            return this.Clone(_frmParent, "");
        }

        virtual public void Copy(Frame _frame)
        {
            this.Size = _frame.Size;
            this.Position = _frame.Position;
            this.m_renderer = _frame.Renderer;
            this.Scale = _frame.Scale;
            this.ScriptControl = _frame.ScriptControl;
            this.ScriptControlID = _frame.ScriptControlID;
            this.ScriptControlPath = _frame.ScriptControlPath;
            this.Visible = _frame.Visible;
            this.m_fZOrder = _frame.ZOrder;
            this.DataSourceIndex = _frame.DataSourceIndex;
            this.DataSource = _frame.DataSource;
            this.DataSourceName = _frame.DataSourceName;
            this.InheritsDataSource = _frame.InheritsDataSource;
            //this.m_strID = _frame.ID; // don't copy _frame.ID !!
            this.RestrictedRendererContexts = _frame.RestrictedRendererContexts;
            this.AutoResize = _frame.AutoResize;
            if (null != _frame.LayoutProcessor)
            {
                this.SetLayout(_frame.LayoutProcessor.Rows,
                    _frame.LayoutProcessor.Columns,
                    _frame.LayoutProcessor.FeedRowFirst);
            }
            if (false != _frame.HasParameters)
            {
                this.m_htblParameters = new Hashtable();
                _frame.GetParameters(this.m_htblParameters);
            }
        }

        virtual public string GetStringData(string _strField)
        {
            if (false != this.NeedUpdateFromDataSource)
            {
                return this.DataSource.GetData<string>(this.DataSourceIndex, _strField);
            }
            return null;
        }

        virtual public void Release()
        {
            if (null != this.m_vertexFeeders)
            {
                this.m_renderer.ReleaseFeeders(this.m_vertexFeeders);
            }
            foreach (Frame child in this.m_lstChildren)
            {
                child.Release();
            }
        }

        virtual protected bool IsInBounds(float _fX, float _fY)
        {
            return ((this.m_f2Position.X - this.m_f2HalfSize.X) <= _fX)
                && ((this.m_f2Position.Y - this.m_f2HalfSize.Y) <= _fY)
                && ((this.m_f2Position.X + this.m_f2HalfSize.X) >= _fX)
                && ((this.m_f2Position.Y + this.m_f2HalfSize.Y) >= _fY);
        }

        virtual protected void UpdateLocalMatrix()
        {
            Matrix matTrans = new Matrix();
            Matrix matScale = new Matrix();

            Matrix.Translation(this.m_f2Position.X, this.m_f2Position.Y, this.m_fZOrder, out matTrans);
            Matrix.Scaling(this.m_f2Scale.X, this.m_f2Scale.Y, 1.0f, out matScale);
            this.m_matLocal = matScale * matTrans;

            this.OnParentWorldMatrixChanged();
            this.OnParentSizeChanged();
        }

        virtual public void UpdateSize()
        {
            if (null != this.m_layoutProcessor)
            {
                foreach (Frame child in this.m_lstChildren)
                {
                    child.UpdateSize();
                }
                this.UpdateLayout();
            }
            else if (false != this.AutoResize)
            {
                float minX = float.MaxValue;
                float minY = float.MaxValue;
                float maxX = float.MinValue;
                float maxY = float.MinValue;
                foreach (Frame child in this.m_lstChildren)
                {
                    child.UpdateSize();
                    float halfWidth = child.Size.X / 2.0f;
                    float halfHeight = child.Size.Y / 2.0f;
                    if (minX > (child.Position.X - halfWidth))
                    {
                        minX = child.Position.X - halfWidth;
                    }
                    if (minY > (child.Position.Y - halfHeight))
                    {
                        minY = child.Position.Y - halfHeight;
                    }
                    if (maxX < (child.Position.X + halfWidth))
                    {
                        maxX = child.Position.X + halfWidth;
                    }
                    if (maxY < (child.Position.Y + halfHeight))
                    {
                        maxY = child.Position.Y + halfHeight;
                    }
                }
                this.Size = new Vector2(maxX - minX, maxY - minY);
            }
            else
            {
                foreach (Frame child in this.m_lstChildren)
                {
                    child.UpdateSize();
                }
            }
        }

        #region IRenderable Membres

        override public List<IRendererContext> RestrictedRendererContexts
        {
            set
            {
                if (value != this.m_lstRestrictedRendererContexts)
                {
                    this.m_lstRestrictedRendererContexts = value;
                    foreach (IRenderable renderable in this.m_lstChildren)
                    {
                        renderable.RestrictedRendererContexts = value;
                    }
                }
            }
        }

        override public void UpdateData(float _deltaTime)
        {
            base.UpdateData(_deltaTime);
            if ((null == this.DataSource) && (false == string.IsNullOrEmpty(this.DataSourceName)))
            {
                DataSourceManager dataSourceManager = this.m_coreContext.GetData<DataSourceManager>("DataSourceManager");
                this.DataSource = dataSourceManager.Get(this.DataSourceName);
            }
        }

        #endregion

        #region IParameterizable Membres

        virtual public string DataSourceName
        {
            get
            {
                return this.m_dataSourceName;
            }
            set
            {
                if (value != this.m_dataSourceName)
                {
                    this.m_dataSourceName = value;
                    this.m_bDirty = true;
                    // automatically "spread" update to children so that they always stay synchronized
                    foreach (Frame child in this.m_lstChildren)
                    {
                        child.DataSourceName = value;
                    }
                }
            }
        }

        virtual public IDataSource DataSource
        {
            get
            {
                return this.m_dataSource;
            }
            set
            {
                if (value != this.m_dataSource)
                {
                    this.m_dataSource = value;
                    this.m_bDirty = true;
                    // default data source index when a valid data source is assigned
                    if ((null != this.m_dataSource) && (-1 == this.DataSourceIndex))
                    {
                        this.DataSourceIndex = 0;
                    }
                    // automatically "spread" update to children so that they always stay synchronized
                    foreach (Frame child in this.m_lstChildren)
                    {
                        child.DataSource = value;
                    }
                }
            }
        }

        virtual public int DataSourceIndex
        {
            get
            {
                return this.m_sDataSourceIndex;
            }
            set
            {
                if (value != this.m_sDataSourceIndex)
                {
                    this.m_sDataSourceIndex = value;
                    this.m_bDirty = true;
                    // automatically "spread" update to children so that they always stay synchronized
                    foreach (Frame child in this.m_lstChildren)
                    {
                        child.DataSourceIndex = value;
                    }
                }
            }
        }

        virtual public bool HasParameters
        {
            get
            {
                return ((null != this.m_htblParameters) && (0 < this.m_htblParameters.Count));
            }
        }

        virtual public bool InheritsDataSource
        {
            get
            {
                return this.m_bInheritsDataSource;
            }
            set
            {
                this.m_bInheritsDataSource = value;
                if (false != this.m_bInheritsDataSource)
                {
                    Frame parent = this.Parent;
                    while (null != parent)
                    {
                        if (null != parent.DataSource)
                        {
                            this.DataSource = parent.DataSource;
                            break;
                        }
                        parent = parent.Parent;
                    }
                }
            }
        }

        virtual public bool AddParameter(string _strSource, string _strDestination)
        {
            if (null == this.m_htblParameters)
            {
                this.m_htblParameters = new Hashtable();
            }
            if (false == this.m_htblParameters.ContainsKey(_strSource))
            {
                this.m_htblParameters.Add(_strSource, _strDestination);
                return true;
            }
            return false;
        }

        virtual public void GetParameters(Hashtable _htblParameters)
        {
            foreach (DictionaryEntry de in this.m_htblParameters)
            {
                _htblParameters.Add(de.Key, de.Value);
            }
        }

        #endregion

        #region ILayout Membres

        virtual public void SetLayout(int _sRows, int _sColumns, bool _bFeedRowFirst)
        {
            if (null == this.m_layoutProcessor)
            {
                this.m_layoutProcessor = new LayoutProcessor(this);
            }
            this.m_layoutProcessor.Rows = _sRows;
            this.m_layoutProcessor.Columns = _sColumns;
            this.m_layoutProcessor.FeedRowFirst = _bFeedRowFirst;
            this.m_bDirty = true;
        }

        virtual public bool NeedLayoutUpdate
        {
            get
            {
                return ((null != this.m_layoutProcessor) && (false != this.m_layoutProcessor.NeedUpdate));
            }
        }

        virtual public void UpdateLayout()
        {
            if (false != this.NeedLayoutUpdate)
            {
                // update will actually be performed if layout config (rows, column, feedrowfirst) or children count have changed 
                this.m_layoutProcessor.Update();
            }
        }

        #endregion
    }
}
